<?PHP
namespace ReflectionEx {
   class ClassInfo
   {
      private $rClass;

      public function __construct($className)
      {
         $this->rClass = new \ReflectionClass($className);
      }

      public function newInstance()
      {
         $args = func_get_args();

         if (empty($args)) {
            return $this->rClass->newInstance();
         } else {
            return $this->rClass->newInstance($args);
         }

      }

      public function hasMethod($methodName)
      {
         return $this->rClass->hasMethod($methodName);
      }

      public function getMethod($methodName)
      {
         $className = $this->rClass->getName();

         return new MethodInfo($className, $methodName);
      }
   }

   class MethodInfo
   {
      private $className;
      private $methodName;

      private $rMethod;

      public function __construct($className, $methodName)
      {
         $this->className = $className;
         $this->methodName = $methodName;

         $this->rMethod = new \ReflectionMethod($className, $methodName);
      }

      public function getParameters()
      {
         $params = $this->rMethod->getParameters();

         $methodInfo = $this;
         $paramsInfo = $this->getMethodMethadata();

         $res = array_map(function ($param) use ($methodInfo, $paramsInfo) {

            $paramName = $param->getName();
            $info = array(
               "name" => $paramName,
               "type" => NULL
            );
            if (isset($paramsInfo['params'][$paramName])) {
               $info = array(
                  "name" => $paramName,
                  "type" => $paramsInfo['params'][$paramName]
               );
            }
            $item = new ParameterInfo($methodInfo, $param, $info);

            return $item;
         }, $params);

         return $res;
      }

      public function getDocComment()
      {
         return $this->rMethod->getDocComment();
      }

      public function invokeArgs($instance, $args)
      {
         return $this->rMethod->invokeArgs($instance, $args);
      }

      public function getAttribute($name)
      {
         $methaData = $this->getMethodMethadata();

         return $methaData["role"];
      }

      public function getMethodMethadata()
      {
         $methodInfo = $this;
         $phpDoc = array(
            'params' => array(),
            'return' => null,
            'role' => array()
         );
         $docComment = $methodInfo->getDocComment();
         if (trim($docComment) == '') {
            return $phpDoc;
         }

         $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
         $docComment = ltrim($docComment, "\r\n");
         $parsedDocComment = $docComment;
         $lineNumber = $firstBlandLineEncountered = 0;
         while (($newlinePos = strpos($parsedDocComment, "\n")) !== false) {
            $lineNumber++;
            $line = substr($parsedDocComment, 0, $newlinePos);

            $matches = array();
            if ((strpos($line, '@') === 0) && (preg_match('#^(@\w+.*?)(\n)(?:@|\r?\n|$)#s', $parsedDocComment, $matches))) {
               $tagDocblockLine = $matches[1];
               $matches2 = array();

               if (!preg_match('#^@(\w+)(\s|$)#', $tagDocblockLine, $matches2)) {
                  break;
               }
               $matches3 = array();
               if (!preg_match('#^@(\w+)\s+([\w|\\\]+)(?:\s+(\$\S+))?(?:\s+(.*))?#s', $tagDocblockLine, $matches3)) {
                  break;
               }
               switch (strtolower($matches3[1])) {
                  case "param":
                     $phpDoc['params'][ltrim($matches3[3], "$")] = $matches3[2];
                     break;
                  case "return":
                     $phpDoc['return'] = array('type' => $matches3[2]);
                     break;
                  case "role":
                     $phpDoc['role'] = $matches3[2];
                     break;
               }

               $parsedDocComment = str_replace($matches[1] . $matches[2], '', $parsedDocComment);
            }
         }

         return $phpDoc;
      }
   }

   class ParameterInfo
   {
      private $rParameter;
      private $info;

      public function getName()
      {
         return $this->rParameter->getName();
      }

      public function __construct($methodInfo, $rParameter, $info)
      {
         $this->rParameter = $rParameter;
         $this->info = $info;
      }

      public function getClass()
      {
         $typeInfo = $this->rParameter->getClass();
         if (empty($typeInfo)) {
            $name = $this->rParameter->getName();
            $typeInfo = $this->info["type"];
         }

         if (!empty($typeInfo))
         {
            return new ClassInfo($typeInfo);
         }

         return NULL;
      }
   }
}